package org.hepeng.workx.util.proxy;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
import org.hepeng.workx.exception.ApplicationRuntimeException;
import org.hepeng.workx.util.ClassMetadata;
import org.hepeng.workx.util.StandardClassMetadata;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;

/**
 * @author he peng
 */
public abstract class AbstractProxyFactory implements ProxyFactory {

    private static final List<InvokeFilter> DEFAULT_INVOKE_FILTERS = Collections.synchronizedList(new ArrayList<>());

    static {
        DEFAULT_INVOKE_FILTERS.add(new NativeInvokeAnnotationFilter());
        DEFAULT_INVOKE_FILTERS.add(new ObjectClassMethodInvokeFilter());
        DEFAULT_INVOKE_FILTERS.add(new StaticMethodInvokeFilter());
        DEFAULT_INVOKE_FILTERS.add(new NativeMethodInvokeFilter());
    }

    @Override
    public Object createProxy(Object target, List<Class<?>> interfaces , List<Invoker> invokers , List<InvokeFilter> filters) {

        Object proxy;
        try {
            ClassMetadata classMetadata = new StandardClassMetadata(getValidTargetClass(target));
            Class<?> superClassOfProxy = getSuperClassOfProxy(classMetadata);
            List<Class<?>> mergedInterfaces = mergeInterfaces(interfaces, classMetadata);
            proxy = doCreateProxyInternal(target , superClassOfProxy , mergedInterfaces , invokers , invokeFilters(filters));
        } catch (Throwable t) {
            throw new ProxyException("create proxy error" , t);
        }

        return proxy;
    }

    @Override
    public Object createProxy(Class targetClass, List<Class<?>> interfaces ,
                              List<Class<?>> constructorArgTypes, List<Object> constructorArgs ,
                              List<Invoker> invokers , List<InvokeFilter> filters) {
        Object proxy;
        try {
            ClassMetadata classMetadata = new StandardClassMetadata(getValidTargetClass(targetClass));
            List<Class<?>> mergedInterfaces = mergeInterfaces(interfaces, classMetadata);
            Class<?> superClassOfProxy = getSuperClassOfProxy(classMetadata);
            proxy = doCreateProxyInternal(superClassOfProxy  , mergedInterfaces ,
                    constructorArgTypes , constructorArgs, invokers , invokeFilters(filters));
        } catch (Throwable t) {
            throw new ProxyException("create proxy error" , t);
        }
        return proxy;
    }

    private List<InvokeFilter> invokeFilters(List<InvokeFilter> filters) {
        List<InvokeFilter> invokeFilters = new ArrayList<>(DEFAULT_INVOKE_FILTERS);
        if (CollectionUtils.isNotEmpty(filters)) {
            invokeFilters.addAll(filters);
        }
        return invokeFilters;
    }

    protected Class<?> getValidTargetClass(Class<?> clazz) {
        Class<?> targetClass = clazz;
        if (ProxyUtils.isProxy(clazz)) {
            targetClass = targetClass.getSuperclass();
        }

        return targetClass;
    }

    protected Class<?> getValidTargetClass(Object obj) {
        Class<?> targetClass = obj.getClass();
        if (ProxyUtils.isProxy(targetClass)) {
            targetClass = targetClass.getSuperclass();
        }

        return targetClass;
    }

    protected Class<?> getSuperClassOfProxy(ClassMetadata classMetadata) throws ClassNotFoundException {
        Class<?> superClass;
        if (classMetadata.isFinal() || classMetadata.isInterface()) {
            superClass = ClassUtils.getClass(classMetadata.getSuperClassName());
        } else {
            superClass = classMetadata.getJavaClass();
        }
        return superClass;
    }

    protected List<Class<?>> mergeInterfaces(List<Class<?>> interfaces , ClassMetadata classMetadata) throws ClassNotFoundException {
        List<Class<?>> mergeInterfaces = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(interfaces)) {
            mergeInterfaces.addAll(new HashSet<>(interfaces));
        }
        if (classMetadata.isInterface()) {
            mergeInterfaces.add(classMetadata.getJavaClass());
        }

        String[] interfaceNames = classMetadata.getInterfaceNames();
        if (ArrayUtils.isNotEmpty(interfaceNames)) {
            for (String interfaceName : interfaceNames) {
                Class<?> interfaceClass = ClassUtils.getClass(interfaceName);
                if (! mergeInterfaces.contains(interfaceClass)) {
                    mergeInterfaces.add(interfaceClass);
                }
            }
        }
        return mergeInterfaces;
    }


    protected Invoker nextInvoker(List<Invoker> invokers , int index) {
        int nextInvokerIndex = index + 1;
        if (nextInvokerIndex == invokers.size()) {
            return null;
        }
        return invokers.get(nextInvokerIndex);
    }

    protected Invocation invocationChain(List<Invoker> invokers , int index , Invocation lastInvocation) {
        Invoker nextInvoker = nextInvoker(invokers, index);
        if (Objects.isNull(nextInvoker)) {
            return lastInvocation;
        }

        return nextInvocation(invokers , index , lastInvocation);
    }

    protected abstract Invocation nextInvocation(List<Invoker> invokers, int index, Invocation lastInvocation);

    protected abstract Object doCreateProxyInternal(
            Class<?> superClassOfProxy, List<Class<?>> interfaces,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs ,
            List<Invoker> invokers , List<InvokeFilter> filters) throws Exception;

    protected abstract Object doCreateProxyInternal(
            Object target, Class<?> superClassOfProxy,
            List<Class<?>> interfaces, List<Invoker> invokers ,
            List<InvokeFilter> filters) throws Exception;
}
